package threewings;

import java.awt.Color;

import javax.media.j3d.Appearance;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.Material;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Texture;
import javax.media.j3d.Texture2D;
import javax.media.j3d.TextureAttributes;
import javax.media.j3d.TriangleArray;
import javax.vecmath.Color3f;
import javax.vecmath.Color4f;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;

import com.sun.j3d.utils.geometry.GeometryInfo;
import com.sun.j3d.utils.geometry.NormalGenerator;

/**
 * Stores chunk information
 *
 * @author Tyler Whitehouse.
 *         Created Oct 21, 2013.
 */
public class Chunk extends BranchGroup {
	
	private Point3f position;
	private float loadedRange;
	
	/**
	 * Get the height at the given point, according to some equation
	 * 
	 * @param x
	 * @param z
	 * @return the height at the coordinate
	 */
	public static float getHeight(float x, float z) {
		return 1f * (float)(Math.sin(0.1*z) + 2f * (float)(Math.sin(0.05*z)) + (float)(Math.cos(0.1*x)*Math.sin(0.3*z)));
		//return 0.001f*x;
	}
	
	/**
	 * gets the normal vector at a point
	 * 
	 * @param x
	 * @param z
	 * @return the normal vector
	 */
	public static Vector3f getNormal(float x, float z) {
		float diff = 0.0001f;
		float h = getHeight(x,z);
		Vector3f dx = new Vector3f(diff, getHeight(x + diff, z)-h, 0f);
		dx.scale(1000);
		Vector3f dz = new Vector3f(0f, getHeight(x, z + diff)-h, diff);
		dz.scale(1000);
		Vector3f result = new Vector3f();

		result.cross(dz, dx);
		
		result.normalize();
		//result.scale(10);
		// System.out.println(result.getY());
		return result;
		// return new Vector3f(0f, 1f, 0f);
	}
	
	/**
	 * Generates a chunk
	 *
	 * @param sx
	 * @param sz
	 * @param ex
	 * @param ez
	 * @param step 
	 */
	public Chunk(float sx, float sz, float ex, float ez,float step) {
		this.position = new Point3f((sx+ex)*0.5f, 0f, (sz+ez)*0.5f);
		this.loadedRange = 50;
		int dx = 1+(int)(Math.abs(ex-sx)/step);
		int dz = 1+(int)(Math.abs(ez-sz)/step);
		int arraySize = dx*dz*2*3;
		arraySize = dx*dz*3;
		//arraySize += 300;
		
		TriangleArray terrain1 = new TriangleArray(arraySize, GeometryArray.COORDINATES);
		TriangleArray terrain2 = new TriangleArray(arraySize, GeometryArray.COORDINATES);
		
		int count = 0;
		for (float x = sx; x <= ex; x += step){
			for (float z = sz; z <= ez; z += step){
				terrain1.setCoordinate(count, new Point3f(x+step,getHeight(x+step,z),z));
				count++;
				terrain1.setCoordinate(count, new Point3f(x,getHeight(x,z),z));
				count++;
				terrain1.setCoordinate(count, new Point3f(x,getHeight(x,z+step),z+step));
				count++;
				
				// all green or green and yellow triangles
				/* *
				terrain1.setCoordinate(count, new Point3f(x,getHeight(x,z+step),z+step));
				count++;
				terrain1.setCoordinate(count, new Point3f(x+step,getHeight(x+step,z+step),z+step));
				count++;
				terrain1.setCoordinate(count, new Point3f(x+step,getHeight(x+step,z),z));
				count++;
				/* */
				terrain2.setCoordinate(count-3, new Point3f(x,getHeight(x,z+step),z+step));
				terrain2.setCoordinate(count-2, new Point3f(x+step,getHeight(x+step,z+step),z+step));
				terrain2.setCoordinate(count-1, new Point3f(x+step,getHeight(x+step,z),z));
				/* */
			}
		}
		
		GeometryInfo geometryInfo1 = new GeometryInfo(terrain1);
		NormalGenerator ng1 = new NormalGenerator();
		ng1.generateNormals(geometryInfo1);
		
		GeometryInfo geometryInfo2 = new GeometryInfo(terrain2);
		NormalGenerator ng2 = new NormalGenerator();
		ng2.generateNormals(geometryInfo2);

		GeometryArray result1 = geometryInfo1.getGeometryArray();
		GeometryArray result2 = geometryInfo2.getGeometryArray();
		
		// green appearance
		Appearance appearance1 = new Appearance();
		Color3f color1 = new Color3f(Color.green);
		Color3f black = new Color3f(0.0f, 0.0f, 0.0f);
		Color3f white = new Color3f(1.0f, 1.0f, 1.0f);
		Texture texture = new Texture2D();
		TextureAttributes texAttr = new TextureAttributes();
		texAttr.setTextureMode(TextureAttributes.MODULATE);
		texture.setBoundaryModeS(Texture.WRAP);
		texture.setBoundaryModeT(Texture.WRAP);
		texture.setBoundaryColor(new Color4f(0.0f, 0.0f, 0.0f, 0.0f));
		Material mat1 = new Material(color1, black, color1, white, 70f);
		appearance1.setTextureAttributes(texAttr);
		appearance1.setMaterial(mat1);
		appearance1.setTexture(texture);
		
		// yellow appearance
		Appearance appearance2 = new Appearance();
		Color3f color2 = new Color3f(Color.yellow);
		Texture texture2 = new Texture2D();
		TextureAttributes texAttr2 = new TextureAttributes();
		texAttr2.setTextureMode(TextureAttributes.MODULATE);
		texture2.setBoundaryModeS(Texture.WRAP);
		texture2.setBoundaryModeT(Texture.WRAP);
		texture2.setBoundaryColor(new Color4f(0.0f, 0.0f, 0.0f, 0.0f));
		Material mat2 = new Material(color2, black, color2, white, 70f);
		appearance2.setTextureAttributes(texAttr2);
		appearance2.setMaterial(mat2);
		appearance2.setTexture(texture2);
		
		// green and yellow shapes
		Shape3D shape1 = new Shape3D(result1, appearance1);
		Shape3D shape2 = new Shape3D(result2, appearance2);
		this.addChild(shape1);
		this.addChild(shape2);
		

	}
	
	/**
	 * Gets the x-coord of the center of the chunk
	 *
	 * @return the x-coord
	 */
	public float getX(){
		return this.position.x;
	}
	
	/**
	 * gets the y-coord of the center of the chunk
	 *
	 * @return the y-coord
	 */
	public float getY(){
		return this.position.z;
	}
	
	public Point3f getPosition(){
		return this.position;
	}
	
	/**
	 * Gets the actual distance between the middle of the chunk and given position
	 * 
	 * @param p
	 * @return the distance
	 */
	public float distance(Point3f p){
		return this.position.distance(p);
	}
	
	/**
	 * Set the range from the glider for which, when outside of, the chunk will return false
	 * 
	 * @param l
	 */
	public void setLoadedRange(float l){
		this.loadedRange = l;
	}
	
	/**
	 * Check if the chunk is within this chunk's range of the point
	 * Absolute distance check (Circular)
	 * @param pos 
	 * 
	 * @return whether it is in range
	 */
	public boolean inLoadedRange(Point3f pos){
		if (this.position.distance(pos) < this.loadedRange)
			return true;
		
		return false;
	}
	
	/**
	 * Check if the chunk is within the given range of the point
	 * Absolute distance check (Circular)
	 * 
	 * @param range
	 * @param pos
	 * @return whether it is in the range or not
	 */
	public boolean inLoadedRange(float range, Point3f pos){
		if (this.position.distance(pos) < range)
			return true;
		
		return false;
	}
	
	/**
	 * Check if the chunk is within this chunk's range of the point
	 * Manhattan distance check (dx + dz)
	 * @param pos 
	 * 
	 * @return whether it is in range
	 */
	public boolean inLoadedRangeM(Point3f pos){
		if (this.position.distanceL1(pos) < this.loadedRange)
			return true;
		
		return false;
	}
	
	/**
	 * Check if the chunk is within the given range of the point
	 * Manhattan distance check (dx + dz)
	 * 
	 * @param range
	 * @param pos
	 * @return the range check in manhattan distance
	 */
	public boolean inLoadedRangeM(float range, Point3f pos){
		if (this.position.distanceL1(pos) < range)
			return true;
		
		return false;
	}
	
	/**
	 * Check if the chunk is within this chunk's range of the point
	 * Rectangular bounds check
	 * @param pos 
	 * 
	 * @return whether it is in range
	 */
	public boolean inLoadedRangeS(Point3f pos){
		if (Math.abs(this.position.x - pos.x) < this.loadedRange &&
			Math.abs(this.position.y - pos.y) < this.loadedRange)
			return true;
		
		return false;
	}
	
	/**
	 * Check if the chunk is within the given range of the point
	 * Rectangular bounds check
	 * 
	 * @param range
	 * @param pos
	 * @return the rectangular bounds check
	 */
	public boolean inLoadedRangeS(float range, Point3f pos){
		if (Math.abs(this.position.x - pos.x) < range &&
			Math.abs(this.position.y - pos.y) < range)
			return true;
		
		return false;
	}

}
